package com.tnovoselec.server.gdjejefrka.util;


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;


public class XMLUtil {

	public static final String XML_CONTENT_TYPE = "application/xml";

	public static final String CHAR_AMPERSAND = "&";
	public static final String CHAR_LESS_THAN = "<";
	public static final String CHAR_GREATER_THAN = ">";
	public static final String CHAR_QUOTE = "\"";
	public static final String CHAR_APOSTROPHE = "'";

	public static final String REF_AMPERSAND = "&amp;";
	public static final String REF_LESS_THAN = "&lt;";
	public static final String REF_GREATER_THAN = "&gt;";
	public static final String REF_QUOTE = "&quot;";
	public static final String REF_APOSTROPHE = "&apos;";

	public static final String CHAR_MATCHER = "(\\&<>\"')";

	/*public static String getValue(Node node) {
	 NodeList nl = node.getChildNodes();
	 String res = getValue(nl);
	 if (res != null && res.trim().length() > 0) {
	 return res;
	 }
	 for (int i = 0; i < nl.getLength(); i++) {
	 node = nl.item(i);
	 if ("name".equalsIgnoreCase(node.getNodeName())) {
	 return getValue(node.getChildNodes());
	 }
	 }
	 return "";
	 }*/

	public static String getNodeAttribute(Node node, String atr, String defaultValue) {
		String res = getNodeAttribute(node, atr);
		if(res == null) {
			return defaultValue;
		}
		return res;
	}

	public static String getNodeAttribute(Node node, String atr) {
		Node n = node.getAttributes().getNamedItem(atr);
		if (n != null) {
			return n.getNodeValue();
		}
		return null;
	}

	public static String getNodeValue(Node node) {
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node _node = nl.item(i);
			if (_node instanceof Text) {
				return _node.getNodeValue();
			}
		}
		return null;
	}

	public static Node findNodeByAttrib(Node node, String nodeName, String attribName, String attribValue) {
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node tmp = nl.item(i);
			if (nodeName.equals(tmp.getNodeName())) {
				//res.add(tmp);
				String a = getNodeAttribute(tmp, attribName);
				if (a != null && a.equals(attribValue)) {
					return tmp;
				}
			}
		}
		return null;
	}

	public static String findNodeValue(Node node, String name) {
		Node nod = findNode(node, name);
		if (nod == null) {
			throw new NullPointerException("node is null!");
		}
		return getNodeValue(nod);
	}

	public static Collection<String> findNodeValues(Node node, String name) {
		Collection<Node> nodes = findAllNodes(node, name);
		Collection<String> values = new LinkedList<String>();
		for(Node n: nodes) {
			values.add(getNodeValue(n));
		}
		return values;
	}

	public static Node findNode(Node node, String name) {
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node tmp = nl.item(i);
			if (name.equals(tmp.getNodeName())) {
				return tmp;
			}
		}
		return null;
	}

	public static List<Node> findAllNodes(Node node, String name) {
		List<Node> res = new LinkedList<Node>();
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node tmp = nl.item(i);
			if (name.equals(tmp.getNodeName())) {
				res.add(tmp);
			}
		}
		return res;
	}

	public static List<Node> findAllNodesContainingName(Node node, String name) {
		List<Node> res = new LinkedList<Node>();
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node tmp = nl.item(i);
			if (tmp.getNodeName().indexOf(name) != -1) {
				res.add(tmp);
			}
		}
		return res;
	}

	/*
	 * Traverses through all parent and child nodes, searches for nodes containing attribute name,
	 * if name matches, node containig attribute is added to result collection.
	 */
	public static void findAllNodesContainingAttribute(Node node, String attributeName, Collection<Node> result) {
		if (node.getAttributes() != null) {
			Node attrn = node.getAttributes().getNamedItem(attributeName);
			if (attrn != null && attributeName.equals(attrn.getNodeName())) {
				result.add(node);
			}
		}

		NodeList nl = node.getChildNodes();
		if (nl != null && nl.getLength() > 0) {
			for (int x = 0; x < nl.getLength(); x++) {
				Node n = nl.item(x);
				findAllNodesContainingAttribute(n, attributeName, result);
			}
		}
	}

	/*
	 * Returns collection of attribute values containing attribute node with specified attribute name.
	 */
	public static Collection<String> findAllAttributeNodes(Collection<Node> nodes, String attributeName) {
		Collection<String> result = new HashSet<String>();
		for (Node n: nodes) {
			Node attrn = n.getAttributes().getNamedItem(attributeName);
			if (attrn != null && attributeName.equals(attrn.getNodeName())) {
				result.add(attrn.getNodeValue());
			}
		}
		return result;
	}

	public static Document getDoc(byte xmlData[]) throws SAXException {
		try {
			return getDoc(new ByteArrayInputStream(xmlData));
		} catch (IOException e) {
			//Can this happen in bytearrayinput strema?
			return null;
		}
	}

	public static Document getDoc(String file) throws SAXException, IOException {
		return getDoc(new BufferedInputStream(new FileInputStream(file)));
	}

	public static DocumentBuilder getDocBuilder() throws SAXException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setValidating(false);
		factory.setNamespaceAware(true);
		DocumentBuilder builder = null;
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new SAXException(e);
		}
		return builder;
	}

	public static Document getDoc(InputStream is) throws SAXException, IOException {
		return getDocBuilder().parse(is);
	}

	public static Document getEmptyDoc() throws SAXException {
		return getDocBuilder().newDocument();
	}

	public static List<Node> getChildNodes(Node parent){
		List<Node> childs = new LinkedList<Node>();
		NodeList nl = parent.getChildNodes();
		for(int i = 0; i<nl.getLength();i++) {
			if(nl.item(i) instanceof Element) {
				childs.add(nl.item(i));
			}
		}
		return childs;
	}

	public static List<String> getNodeAttributes(Node nd){
		List<String> att = new LinkedList<String>();
		NamedNodeMap nm = nd.getAttributes();
		if(nm != null) {
			for(int i = 0;i<nm.getLength();i++) {
				Attr n = (Attr)nm.item(i);
				att.add(n.getName());
			}
		}
		return att;
	}

	/*
	 * Serializes DOM node to string as XML
	 */
	public static String serialize(Document doc, String encoding) throws IOException {
	    try {
	    	ByteArrayOutputStream out = new ByteArrayOutputStream();
	    	serialize(doc.getDocumentElement(), out, encoding);
	    	return out.toString(encoding);
	    } catch (Exception e) {
	        throw new IOException("XML document to string error.");
	    }
	}

	/*
	 * Serializes DOM node to string as XML
	 */
	public static String serialize(Document doc) throws IOException {
		return serialize(doc, "UTF-8");
	}

	public static void serialize(Node node, OutputStream out, String encoding) throws IOException {
		try {
	        Source source = new DOMSource(node);
	        Result result = new StreamResult(new OutputStreamWriter(out, encoding));

	        TransformerFactory tf = TransformerFactory.newInstance();
	        
	        try
	        {
	        	tf.setAttribute("indent-number", new Integer(2));
	        }
	        catch (IllegalArgumentException e)
	        {
	        	// JDK 1.6 FIX
	        	// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519088 
	        }
	        
	        Transformer transformer = tf.newTransformer();
	        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
	        transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
	        transformer.transform(source, result);
	    } catch (TransformerConfigurationException e) {
	        throw new RuntimeException("XML document to output stream error.", e);
	    } catch (TransformerException e) {
	        throw new RuntimeException("XML document to output stream error.", e);
	    }
	}
	/*
	 * Serializes DOM node to output stream as XML
	 */
	public static void serialize(Node node, OutputStream out) throws IOException {
	    serialize(node, out, "UTF-8");
	}

	public static void serialize(Node node, OutputStream out, Map<String, String> outputProperties) throws IOException {
	    try {
	        Source source = new DOMSource(node);
	        Result result = new StreamResult(out);

	        Transformer transformer = TransformerFactory.newInstance().newTransformer();
	        if (outputProperties.isEmpty()) {
	        	transformer.setOutputProperty(OutputKeys.INDENT, "yes");
	        }
	        for (String outKey : outputProperties.keySet()) {
	        	transformer.setOutputProperty(outKey, outputProperties.get(outKey));
	        }
	        transformer.transform(source, result);
	    } catch (TransformerConfigurationException e) {
	        throw new IOException("XML document to output stream error.");
	    } catch (TransformerException e) {
	        throw new IOException("XML document to output stream error.");
	    }
	}

	//TODO this code stinks, it should be done char by char and ... to many replaces, it can be done in one cycle...
	public static String replaceXMLEntities(String value) {
		if(value == null) return null;
		return value.replaceAll(CHAR_AMPERSAND, REF_AMPERSAND).replaceAll(CHAR_APOSTROPHE, REF_APOSTROPHE)
		.replaceAll(CHAR_GREATER_THAN, REF_GREATER_THAN).replaceAll(CHAR_LESS_THAN, REF_LESS_THAN)
		.replaceAll(CHAR_QUOTE, REF_QUOTE);//.replaceAll("\r\n|\r|\n", "\\r\\n");
	}

	public static String nodeToString(Node node) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		XMLUtil.serialize(node, bos);
		return new String(bos.toByteArray());
	}

	public static String nodeToString(Node node, String encoding) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		XMLUtil.serialize(node, bos);
		return new String(bos.toByteArray(), encoding);
	}

	public static Element createElement(Document doc, String elementName, String elemValue, Element parent) {
		Element ee = createElement(doc, elementName, parent);
		if(elemValue != null) {
			ee.appendChild(doc.createTextNode(elemValue));
		}
		return ee;
	}

	public static Element createElement(Document doc, String elementName, Element parent) {
		Element elem = doc.createElement(elementName);
		if(parent != null) {
			parent.appendChild(elem);
		}
		return elem;
	}

	public static void addAttirbute(Element elem, String attName, String attValue) {
		elem.setAttribute(attName, attValue);
	}
	
	public static Element createCDATAElement(Document document, String elementName, String elemValue, Element parent) {
		Element element = XMLUtil.createElement(document, elementName, parent);
		if (elemValue != null) {
			element.appendChild(document.createCDATASection(elemValue));
		}
		return element;
	}
	
	public static String getChildNodeValue(Node item, String childName) {
		Node node = XMLUtil.findNode(item, childName);
		if (null == node) {
			return null;
		}
		return XMLUtil.getNodeValue(node);
	}

	public static String getChildNodeAttributeValue(Node item, String childName, String atr) {
		Node node = XMLUtil.findNode(item, childName);
		if (null == node) {
			return null;
		}

		return XMLUtil.getNodeAttribute(node, atr);
	}
	
	public static void serialize(Source source, OutputStream out, Map<String, String> outputProperties) throws Exception {
		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			for (Map.Entry<String, String> entry : outputProperties.entrySet()) {
				transformer.setOutputProperty(entry.getKey(), entry.getValue());
				
			}
			
			transformer.transform(source, new StreamResult(out));
			
		} catch (TransformerConfigurationException ex) {
			throw new Exception("XML document to output stream error.", ex);
		} catch (TransformerException ex) {
			throw new Exception("XML document to output stream error.", ex);
		}
	}
	
	/**
	 * CreateTestedCDATAElement
	 * Creates CDATA element but only if value is not empty
	 * @param Document document
	 * @param String elementName
	 * @param String elemValue
	 * @param Element parent
	 * @return Element element
	 * */
//	public static Element createTestedCDATAElement(Document document, String elementName, String elemValue, Element parent) {
//		if (elemValue != null && !Util.isEmpty(elemValue)) {
//			Element element = XMLUtil.createElement(document, elementName, parent);
//			element.appendChild(document.createCDATASection(elemValue));
//			return element;
//		}
//		return null;
//	}	
}